#ifndef UHM_DISCRETE_BASE_OPS_HXX_
#define UHM_DISCRETE_BASE_OPS_HXX_

namespace uhm {
  template<class T_> 
  Int_ op_leaf_to_me_seq(typename T_::Tnd_ *me, 
                         Int_ (*op_func)(typename T_::Tnd_*)) ;
  template<class T_>
  Int_ op_me_to_leaf_seq(typename T_::Tnd_ *me,
                         Int_ (*op_func)(typename T_::Tnd_*));
  template<class T_> 
  Int_ op_leaf_to_me_par(typename T_::Tnd_ *me, 
                         Int_ (*op_func)(typename T_::Tnd_*)) ;
  template<class T_>
  Int_ op_me_to_leaf_par(typename T_::Tnd_* me,
                         Int_ (*op_func)(typename T_::Tnd_*));
  template<class T_> 
  Int_ op_leaf_to_root_seq(T_ &me, Int_ (*op_func)(typename T_::Tnd_*));
	
  template<class T_> 
  Int_ op_root_to_leaf_seq(T_ &me, Int_ (*op_func)(typename T_::Tnd_*)) ;
	
  template<class T_> 
  Int_ op_tree_par(T_ &me, Int_ (*op_func)(typename T_::Tnd_*));

  template<class T_> 
  Int_ op_tree_level_par(T_ &me, Int_ (*op_func)(typename T_::Tnd_*));
	
  // ** Operation
  template<class T_> 
  inline Int_ op_leaf_to_me_seq(typename T_::Tnd_ *me, 
                         Int_ (*op_func)(typename T_::Tnd_*)) {
    for (Int_ i=0;i<me->get_n_children();++i) {
      typename T_::Tnd_ *c = me->get_child(i);

      UHM_ERROR(op_leaf_to_me_seq<T_>(c, op_func), UHM_ERROR_DISCRETE_OP_FUNCT);
    }

    UHM_ERROR(op_func(me), UHM_ERROR_DISCRETE_OP_FUNCT);

    return true;
  }

  template<class T_>
  inline Int_ op_me_to_leaf_seq(typename T_::Tnd_ *me,
                         Int_ (*op_func)(typename T_::Tnd_*)) {
    UHM_ERROR(op_func(me), UHM_ERROR_DISCRETE_OP_FUNCT);

    for (Int_ i=0;i<me->get_n_children();++i) {
      typename T_::Tnd_ *c = me->get_child(i);

      UHM_ERROR(op_me_to_leaf_seq<T_>(c, op_func), UHM_ERROR_DISCRETE_OP_FUNCT);
    }

    return true;
  }


  // ** Multithreading
  template<class T_> 
  inline Int_ op_leaf_to_me_par(typename T_::Tnd_ *me, 
                         Int_ (*op_func)(typename T_::Tnd_*)) {
    for (Int_ i=0;i<me->get_n_children();++i) {
      typename T_::Tnd_ *c = me->get_child(i);

#pragma omp task firstprivate(c) 
      {
        UHM_ERROR(op_leaf_to_me_par<T_>(c, op_func), UHM_ERROR_DISCRETE_OP_FUNCT);
      }
    }

#pragma omp taskwait

    UHM_ERROR(op_func(me), UHM_ERROR_DISCRETE_OP_FUNCT);

    return true;
  }

  template<class T_>
  inline Int_ op_me_to_leaf_par(typename T_::Tnd_* me,
                         Int_ (*op_func)(typename T_::Tnd_*)) {

    UHM_ERROR(op_func(me), UHM_ERROR_DISCRETE_OP_FUNCT);

#pragma omp taskwait

    for (Int_ i=0;i<me->get_n_children();++i) {
      typename T_::Tnd_ *c = me->get_child(i);

#pragma omp task firstprivate(c) 
      {
        UHM_ERROR(op_me_to_leaf_par<T_>(c, op_func), UHM_ERROR_DISCRETE_OP_FUNCT);
      }
    }

    return true;
  }

  template<class T_> 
  inline Int_ op_leaf_to_root_seq(T_ &me, Int_ (*op_func)(typename T_::Tnd_*)) { 
    for (Int_ i=0;i<me.get_size();++i) {
      UHM_ERROR(op_func(me.get_tnd_from_sorted(i)), UHM_ERROR_DISCRETE_OP_FUNCT);
    }
    return true;
  }

  template<class T_> 
  inline Int_ op_root_to_leaf_seq(T_ &me, Int_ (*op_func)(typename T_::Tnd_*)) { 
    for (Int_ i=(me.get_size()-1);i>-1;--i) {
      UHM_ERROR(op_func(me.get_tnd_from_sorted(i)), UHM_ERROR_DISCRETE_OP_FUNCT);
    }
    return true;
  }


  template<class T_> 
  inline Int_ op_tree_par(T_ &me, Int_ (*op_func)(typename T_::Tnd_*)) { 
#pragma omp parallel for schedule(static)
    for (Int_ i=0;i<me.get_size();++i) {
      UHM_ERROR(op_func(me.get_tnd_from_sorted(i)), UHM_ERROR_DISCRETE_OP_FUNCT);
    }
    return true;
  }

  template<class T_> 
  inline Int_ op_tree_level_par(T_ &me, Int_ (*op_func)(typename T_::Tnd_*)) { 
    Int_ prev = 0;
    for (Int_ i=0;i<me.get_size();++i) {
#pragma omp task firstprivate(i)
      {
        UHM_ERROR(op_func(me.get_tnd_from_sorted(i)), UHM_ERROR_DISCRETE_OP_FUNCT);
      }
      if (me.get_tnd_from_sorted(i)->get_gen() != prev) {
#pragma omp taskwait
        prev = me.get_tnd_from_sorted(i)->get_gen();
      }
    }
    return true;
  }


}

#endif
